#include "qbci.h"
#include <QThread>
#include <algorithm>

namespace bci {
// For Wait BciMod start or stop
constexpr int WAIT_TIMEOUT_TIME = 10000; //[ms]
constexpr int SLEEP_TIME = 10000;        //[us]
const QString QBci::modTypeName(const ModType &t) {
  switch (t) {
  case Source:
    return "Source";
  case Analyzer:
    return "Analyzer";
  case App:
    return "App";
  case Other:
    return "Other";
  }
  return nullptr;
}
QBci::QBci() : QObject(), cxt(1, "QBci"), mConfig(new QBciConfig(this)) {
  qRegisterMetaType<QBciConfig *>("QBciConfig*");
  qRegisterMetaType<const QBciConfig *>("const QBciConfig*");
  qRegisterMetaType<::bci::QBciConfig *>("::bci::QBciConfig*");
  qRegisterMetaType<const ::bci::QBciConfig *>("const ::bci::QBciConfig*");
  qRegisterMetaType<::bci::QBciContext *>("::bci::QBciContext *");

  qRegisterMetaType<::bci::QBciSignal>("::bci::QBciSignal");
  qRegisterMetaType<::bci::QBciArmaSignal>("::bci::QBciArmaSignal");
  qRegisterMetaType<QSharedPointer<::bci::QBciSignal>>(
      "QSharedPointer< ::bci::QBciSignal>");
  qRegisterMetaType<QSharedPointer<::bci::QBciArmaSignal>>(
      "QSharedPointer< ::bci::QBciArmaSignal>");

  qRegisterMetaType<::bci::QBciRes>("bci::QBciRes");
  qRegisterMetaType<::bci::QBciRes>("const::bci::QBciRes&"); // this is hack

  cxt.mainThread = QThread::currentThread();
}
QBci::QBci(QBciSource *source, QBciAnalyzer *analyzer, QBciApp *app) : QBci() {
  addSoruce(source);
  addAnalyzer(analyzer);
  addApp(app);
}

QBci::~QBci() {
  QVector<QString> ids;
  foreach (auto &i, ModTable) { ids.append(i.mod->id()); }
  foreach (auto &id, ids) { privateRemoveMod(id); }
}

QBciMod *QBci::getModBy(const ModType &type) {
  switch (type) {
  case Source:
    return mSrc;
  case Analyzer:
    return mAlz;
  case App:
    return mApp;
  case Other:
    return nullptr;
  }
  return nullptr;
}

QBciMod *QBci::getModBy(const QString &id) {
  auto ret = ModTable.find(id);
  return ret != ModTable.end() ? ret->mod : nullptr;
}

void QBci::start() {
  if (isRunning) {
    debug("No need to start a started");
    return;
  }
  debug(QString("QBci started") + "::work on thread: " +
        QBciDebug::threadId2QStr(QThread::currentThreadId()));
  // Check Src Alz App exist.
  foreach (ModType type, QList<ModType>({Source, Analyzer, App})) {
    QBciMod *mod = nullptr;
    switch (type) {
    case Source:
      mod = mSrc;
      break;
    case Analyzer:
      mod = mAlz;
      break;
    case App:
      mod = mApp;
      break;
    case Other:
      Q_ASSERT(0);
    }
    if (!mod) {
      error(modTypeName(type) + " module hasn\'t be set.");
      return;
    }
  }
  auto modNum = ModTable.count();

  // determining the number of semaphore
  cxt.sem = QSharedPointer<QSemaphore>::create(modNum);
  // detect current thread
  cxt.mainThread = QThread::currentThread();
  foreach (auto info, ModTable) {
    QBciMod *mod = info.mod;
    connect(this, &QBci::toStart, mod, &QBciMod::protectedStartSlot);
    connect(this, &QBci::toStop, mod, &QBciMod::protectedStopSlot);
  }
  connect(mSrc, &QBciSource::sendData, mAlz, &QBciAnalyzer::recvData);
  connect(mAlz, &QBciAnalyzer::sendRes, mApp, &QBciApp::recvRes);

  emit toStart(&cxt);

  QThread::usleep(SLEEP_TIME);
  if (!cxt.sem->tryAcquire(modNum, WAIT_TIMEOUT_TIME)) {
    // Handling error
    error("Cannot start all modules, " +
          QString::number(modNum - cxt.sem->available()) +
          " module(s) failed.");

    foreach (auto info, ModTable) {
      QBciMod *mod = info.mod;
      disconnect(this, &QBci::toStart, mod, &QBciMod::protectedStartSlot);
      disconnect(this, &QBci::toStop, mod, &QBciMod::protectedStopSlot);
    }
    disconnect(mSrc, &QBciSource::sendData, mAlz, &QBciAnalyzer::recvData);
    disconnect(mAlz, &QBciAnalyzer::sendRes, mApp, &QBciApp::recvRes);
    return;
  }

  info("All modules started!");
  isRunning = true;
}
void QBci::stop() {
  if (!isRunning) {
    debug("No need to stop a stopped");
    return;
  }
  disconnect(mSrc, &QBciSource::sendData, mAlz, &QBciAnalyzer::recvData);
  disconnect(mAlz, &QBciAnalyzer::sendRes, mApp, &QBciApp::recvRes);

  int modNum = ModTable.count();

  // Stop mSrc first, then mAlz, then mApp
  cxt.sem->release(modNum);
  disconnect(this, &QBci::toStop, mSrc, &QBciMod::protectedStopSlot);
  QMetaObject::invokeMethod(mSrc, &QBciMod::protectedStopSlot,
                            Qt::BlockingQueuedConnection);
  disconnect(this, &QBci::toStop, mAlz, &QBciMod::protectedStopSlot);
  QMetaObject::invokeMethod(mAlz, &QBciMod::protectedStopSlot,
                            Qt::BlockingQueuedConnection);
  disconnect(this, &QBci::toStop, mApp, &QBciMod::protectedStopSlot);
  QMetaObject::invokeMethod(mApp, &QBciMod::protectedStopSlot,
                            Qt::BlockingQueuedConnection);

  emit toStop();
  QThread::usleep(SLEEP_TIME);
  if (!cxt.sem->tryAcquire(modNum, WAIT_TIMEOUT_TIME)) {
    error("Cannot stop all modules, " +
          QString::number(modNum - cxt.sem->available()) +
          " module(s) failed.");
    return;
  }

  foreach (auto info, ModTable) {
    QBciMod *mod = info.mod;
    disconnect(this, &QBci::toStart, mod, &QBciMod::protectedStartSlot);
    disconnect(this, &QBci::toStop, mod, &QBciMod::protectedStopSlot);
  }

  info("All modules stopped!");
  isRunning = false;
}

QBci::ModInfo *QBci::findModuleInfo(const QString &id) {
  auto item = ModTable.find(id);
  if (item == ModTable.end()) {
    return nullptr;
  } else {
    return &(*item);
  }
}
void QBci::privateAddModInfo(ModInfo i) {
  Q_ASSERT(!ModTable.contains(i.mod->id()));
  Q_ASSERT(i.mod->bci == nullptr); // One mod only can be add to one bci
  ModTable.insert(i.mod->id(), i);
}
void QBci::privateAddMod(QBciMod *mod, ModType type, bool isAttach) {
  ModInfo i;
  i.mod = mod;
  i.type = type;
  if (isAttach) {
    i.thread = pushThread(mod->id());
    mod->moveToThread(i.thread);
  } else {
    i.thread = nullptr;
  }
  switch (type) {
  case Source:
    mSrc = dynamic_cast<QBciSource *>(mod);
    break;
  case Analyzer:
    mAlz = dynamic_cast<QBciAnalyzer *>(mod);
    break;
  case App:
    mApp = dynamic_cast<QBciApp *>(mod);
    break;
  case Other:
    break;
  }
  privateAddModInfo(i);
}
void QBci::addSoruce(QBciSource *src, const bool &isAttach) {
  privateAddMod(src, Source, isAttach);
}
void QBci::addAnalyzer(QBciAnalyzer *alz, const bool &isAttach) {
  privateAddMod(alz, Analyzer, isAttach);
}
void QBci::addApp(QBciApp *app, const bool &isAttach) {
  privateAddMod(app, App, isAttach);
}
void QBci::addMod(QBciMod *mod, const bool &isAttach) {
  privateAddMod(mod, Other, isAttach);
}
void QBci::privateRemoveMod(const QString &id) {
  Q_ASSERT(ModTable.contains(id));
  auto &i = ModTable[id];
  delete i.mod;
  if (i.thread)
    popThread(id);
  ModTable.remove(id);
}
void QBci::privateRemoveSAA(ModType type) {
  auto rf = [this](QBciMod *mod) {
    if (mod)
      this->privateRemoveMod(mod->id());
  };
  switch (type) {
  case Source:
    rf(mSrc);
    mSrc = nullptr;
    break;
  case Analyzer:
    rf(mAlz);
    mAlz = nullptr;
    break;
  case App:
    rf(mApp);
    mApp = nullptr;
    break;
  case Other:
    break;
  }
}
void QBci::removeSource() { privateRemoveSAA(Source); }
void QBci::removeAnalyzer() { privateRemoveSAA(Analyzer); }
void QBci::removeApp() { privateRemoveSAA(App); }
void QBci::removeMod(const QString &id) { privateRemoveMod(id); }

bool QBci::isContainMod(QBciMod *mod) const { return isContainMod(mod->id()); }

bool QBci::isContainMod(const QString &id) const {
  return ModTable.contains(id);
}

void QBci::updateConfig(const QString &filename) {
  auto invoke = [this](QBciMod *mod) {
    QMetaObject::invokeMethod(mod, "updateConfig", Qt::AutoConnection,
                              Q_ARG(const ::bci::QBciConfig *, this->mConfig));
  };
  if (mConfig->loadFromFile(filename)) {
    for (auto it = ModTable.begin(); it != ModTable.end(); ++it) {
      invoke(it->mod);
    }
  } else {
    cxt.logger->error("Load Config Failed!");
  }
}

void QBci::error(const QString &mesg) { cxt.logger->error(mesg); }
void QBci::warn(const QString &mesg) { cxt.logger->warn(mesg); }
void QBci::info(const QString &mesg) { cxt.logger->info(mesg); }
void QBci::debug(const QString &mesg) { cxt.logger->debug(mesg); }

QThread *QBci::pushThread(const QString &id) {
  // the return value is only for check or borrow
  Q_ASSERT(!threadTable.contains(id));
  auto ret = threadTable.insert(id, QSharedPointer<QThread>::create())->data();
  ret->start();
  return ret;
}

void QBci::popThread(const QString &id) {
  Q_ASSERT(threadTable.contains(id));
  auto thread = threadTable[id];
  thread->quit();
  thread->wait();
  threadTable.remove(id);
}
} // namespace bci
